/*
 *  Copyright 2008 Mark Ashworth <javameme@gmail.com>.
 * 
 *  Licensed under the GNU General Public License v3 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 * 
 *       http://www.gnu.org/licenses/gpl-3.0.html
 * 
 *  Unless required by applicable law or agreed to in writing, software
 *  distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 *  under the License.
 */
package xperiment.metaphor.service.action.impl;

import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.providers.dao.SaltSource;
import org.springframework.security.providers.encoding.PasswordEncoder;
import org.springframework.security.providers.encoding.PlaintextPasswordEncoder;
import org.springframework.security.userdetails.UserDetails;
import xperiment.metaphor.model.people.user.User;
import xperiment.metaphor.service.action.PasswordEncoderPersistAction;
import xperiment.metaphor.service.user.UserService;

/**
 * Conditionally encodes the password of a User instance if it has changed using
 * the password encoder and salt source.
 * 
 * @author Mark Ashworth <javameme@gmail.com>
 * @version 1.0.0
 */
public class DefaultPasswordEncoderPersistAction extends AbstractPersistAction implements PasswordEncoderPersistAction, BeanFactoryAware {

    private SaltSource saltSource;
    private PasswordEncoder passwordEncoder = new PlaintextPasswordEncoder();
    private UserService userService;
    private BeanFactory beanFactory;
    private String userServiceName = "userService";
    
    /**
     * The password encoder
     * @return PasswordEncoder
     */
    public PasswordEncoder getPasswordEncoder() {
        return passwordEncoder;
    }

    /**
     * The password encoder
     * @param passwordEncoder The new value
     */
    public void setPasswordEncoder(PasswordEncoder passwordEncoder) {
        this.passwordEncoder = passwordEncoder;
    }

    /**
     * The salt source
     * @return SaltSource
     */
    public SaltSource getSaltSource() {
        return saltSource;
    }

    /**
     * The salt source
     * @param saltSource The new value
     */
    @Required
    public void setSaltSource(SaltSource saltSource) {
        this.saltSource = saltSource;
    }

    /**
     * The user service
     * @param userService The new value
     */
    @Override
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    /**
     * Sets the bean factory that the persist action should use
     * @param beanFactory The new value
     */
    @Override
    public void setBeanFactory(BeanFactory beanFactory) {
        this.beanFactory = beanFactory;
    }

    /**
     * The user service bean name in the bean factory. This is used to locate
     * the UserService in the bean factory if the userService is null.
     * @param userServiceName The name of the UserService
     */
    public void setUserServiceName(String userServiceName) {
        this.userServiceName = userServiceName;
    }
    
    /**
     * Conditionally encodes the password using the defined saltSource and
     * passwordEncoder. 
     * 
     * @param object The object to encode the password
     * @return Object
     */
    @Override
    public Object before(Object object) {
        
        if (userService == null && beanFactory == null) {
            logger.warn("The password was not encoded, the user service is null and the bean factory is null");
            return object;
        } else if (userService == null && beanFactory != null) {
            logger.info("Resolving user service from the bean factory");
            
            if (!beanFactory.containsBean(userServiceName)) {
                logger.warn("The bean factory does not contain a bean with the name {}", userServiceName);
                return object;
            } else if (!beanFactory.isTypeMatch(userServiceName, UserService.class)) {
                logger.warn("The bean {} is not of type {}", userServiceName, UserService.class);
                return object;
            } else {
                userService = (UserService) beanFactory.getBean(userServiceName, UserService.class);
                logger.info("The user service has been resolved to {}", userService);
            }
        }
        
        if (passwordEncoder == null) {
            logger.warn("The password was not encoded, the password encoder is null");
            return object;
        }
        if (saltSource == null) {
            logger.warn("The password was not encoded, the salt source is null");
            return object;
        }
        
        if (object == null) {
            logger.warn("The password was not encoded, the object passed was null");
            return object;
        } else if (!(object instanceof User)) {
            logger.warn("The password was not encoded, expected type {} but received {}", User.class.getName(), object.getClass().getName());
            return object;
        }
  
        User user = (User) object;
        
        boolean encodePassword = false;
        User savedUser = this.userService.retrieveByUserName(user.getUsername());
        if (savedUser != null && !user.getPassword().equals(savedUser.getPassword())) {
            /* The password has changed */
            encodePassword = true;
        } else if (savedUser == null) {
            encodePassword = true;
        }
        
        if (encodePassword) {
            Object salt = saltSource.getSalt((UserDetails) user);
            user.setPassword(passwordEncoder.encodePassword(user.getPassword(), salt));
        }
        return user;
    }
}
